package pl.mchaniewski.ryneczek.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.springframework.transaction.annotation.Transactional;

import pl.mchaniewski.ryneczek.dao.GenericDao;

@Transactional(readOnly = true)
public abstract class HibernateGenericDao<T, ID extends Serializable>
        implements GenericDao<T, ID> {
    private EntityManager entityManager;
    private Class<T> persistentClass;

    @PersistenceContext
    public void setEntityManager(EntityManager em) {
        this.entityManager = em;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    @SuppressWarnings("unchecked")
    public HibernateGenericDao() {
        Type[] types = ((ParameterizedType) getClass().getGenericSuperclass())
                .getActualTypeArguments();

        if (types[0] instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) types[0];
            this.persistentClass = (Class<T>) type.getRawType();
        } else {
            this.persistentClass = (Class<T>) types[0];
        }
    }

    public HibernateGenericDao(Class<T> clazz) {
        this.persistentClass = clazz;
    }

    @Override
    public List<T> loadAll() {
        return findByCriteria();
    }

    @SuppressWarnings("unchecked")
    @Override
    public T findById(ID id) {
        Session session = (Session) getEntityManager().getDelegate();

        return (T) session.get(getPersistentClass(), id);
    }

    @Override
    @Transactional(readOnly = false)
    public T save(T entity) {
        return getEntityManager().merge(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void remove(T entity) {
        getEntityManager().remove(entity);
    }

    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Criterion... criterion) {
        Session session = (Session) getEntityManager().getDelegate();
        Criteria crit = session.createCriteria(getPersistentClass());
        for (Criterion c : criterion) {
            crit.add(c);
        }

        return crit.list();
    }

}
